package domains;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import config.GlobalConfig;
import controlFlowGraph.CFG;
import controlFlowGraph.CFGEdge;
import controlFlowGraph.CFGNode;
import controlFlowGraph.Expression;
import controlFlowGraph.LabAssign;
import controlFlowGraph.LabLoad;
import controlFlowGraph.LabNop;
import controlFlowGraph.Variable;

public class TrulyLiveVariablesAnalysis extends Analysis {

	@Override
	public AnalysisDomain getBottom() {
		return new TrulyLiveVariables(new HashSet<Variable>());
	}

	@Override
	public AnalysisDomain getTop() {
		//TODO: possibility to declare a variable as live after
		// the program has ended?
		return new TrulyLiveVariables(GlobalConfig.variableSet);
	}

	@Override
	public AnalysisDomain join(AnalysisDomain left, AnalysisDomain right) {
		TrulyLiveVariables l = (TrulyLiveVariables) left;
		TrulyLiveVariables r = (TrulyLiveVariables) right;
		Set<Variable> res = new HashSet<Variable>();
		res.addAll(l.getTrulyLiveVar());
		res.addAll(r.getTrulyLiveVar());
		return new TrulyLiveVariables(res);
	}

	@Override
	public AnalysisDomain meet(AnalysisDomain left, AnalysisDomain right) {
		TrulyLiveVariables l = (TrulyLiveVariables) left;
		TrulyLiveVariables r = (TrulyLiveVariables) right;
		Set<Variable> res = new HashSet<Variable>();
		res.addAll(l.getTrulyLiveVar());
		res.retainAll(r.getTrulyLiveVar());
		return new TrulyLiveVariables(res);
	}

	@Override
	public AnalysisDomain widening(AnalysisDomain left, AnalysisDomain right) {
		TrulyLiveVariables l = (TrulyLiveVariables) left;
		TrulyLiveVariables r = (TrulyLiveVariables) right;
		Set<Variable> res = new HashSet<Variable>();
		res.addAll(l.getTrulyLiveVar());
		res.addAll(r.getTrulyLiveVar());
		return new TrulyLiveVariables(res);
	}

	@Override
	public AnalysisDomain narrowing(AnalysisDomain left, AnalysisDomain right) {
		TrulyLiveVariables l = (TrulyLiveVariables) left;
		TrulyLiveVariables r = (TrulyLiveVariables) right;
		Set<Variable> res = new HashSet<Variable>();
		res.addAll(l.getTrulyLiveVar());
		res.retainAll(r.getTrulyLiveVar());
		return new TrulyLiveVariables(res);
	}

	@Override
	public String printNode(CFGNode node) {
		Set<Variable> set = ((TrulyLiveVariables) node.getAnalysisInfo()).getTrulyLiveVar();
		String res = "";
		if (node.getId() == 0 || node.getId() == -1) {
			if (node.getId() == 0) {
				res += "START : {";
			} else {
				res += "END : {";
			}
		} else {
			res = "P" + node.getId() + " : {";
		}
		String[] variables = new String[set.size()];
		int i = 0;
		for (Variable v : set) {
			variables[i] = v.toString();
			i++;
		}
		Arrays.sort(variables);
		int size = set.size();
		for (String s : variables) {
			res += s;
			if (size > 1) {
				res += ", ";
				size--;
			}
		}
		res += "}";
		return res;
	}
	
	@Override
	public AnalysisDomain edgeEffectNOP(CFGEdge edge) {
		Set<Variable> res = new HashSet<Variable>();
		res.addAll(((TrulyLiveVariables) edge.getToNode().getAnalysisInfo()).getTrulyLiveVar());
		return new TrulyLiveVariables(res);
	}
	
	@Override
	public AnalysisDomain edgeEffectPos(CFGEdge edge) {
		Set<Variable> res = new HashSet<Variable>(); 
		res.addAll(((TrulyLiveVariables) edge.getToNode().getAnalysisInfo()).getTrulyLiveVar());
		Set<Expression> exp = new HashSet<Expression>();
		exp.addAll(edge.getLabel().getInvolvedExpressions());
		for(Expression e:exp){
			res.addAll(e.getInvolvedVariables());
		}
		return new TrulyLiveVariables(res);
	}
	
	@Override
	public AnalysisDomain edgeEffectNeg(CFGEdge edge) {
		Set<Variable> res = new HashSet<Variable>(); 
		res.addAll(((TrulyLiveVariables) edge.getToNode().getAnalysisInfo()).getTrulyLiveVar());
		Set<Expression> exp = new HashSet<Expression>();
		exp.addAll(edge.getLabel().getInvolvedExpressions());
		for(Expression e:exp){
			res.addAll(e.getInvolvedVariables());
		}
		return new TrulyLiveVariables(res);
	}
	
	@Override
	public AnalysisDomain edgeEffectAssign(CFGEdge edge) {
		Set<Variable> input = new HashSet<Variable>();
		Set<Variable> res = new HashSet<Variable>();
		input.addAll(((TrulyLiveVariables) edge.getToNode().getAnalysisInfo()).getTrulyLiveVar());		
		Variable left = ((LabAssign) edge.getLabel()).getLeftSide();
		res.addAll(input);
		//System.out.println("input: "+res);
		res.remove(left);
		//System.out.println("after removing: "+res);
		if(input.contains(left)){
			Set<Expression> right = ((LabAssign) edge.getLabel()).getInvolvedExpressions();
			for(Expression exp: right){
				res.addAll(exp.getInvolvedVariables());
			}
			//System.out.println("assignment to live variable: "+res);
		}
		return new TrulyLiveVariables(res);
	}
	
	@Override
	public AnalysisDomain edgeEffectLoad(CFGEdge edge) {
		Set<Variable> input = new HashSet<Variable>();
		Set<Variable> res = new HashSet<Variable>();
		input.addAll(((TrulyLiveVariables) edge.getToNode().getAnalysisInfo()).getTrulyLiveVar());		
		Variable left = ((LabLoad) edge.getLabel()).getLeftSide();
		res.addAll(input);
		res.remove(left);
		if(input.contains(left)){
			Set<Expression> right = ((LabLoad) edge.getLabel()).getInvolvedExpressions();
			for(Expression exp: right){
				res.addAll(exp.getInvolvedVariables());
			}
		}
		return new TrulyLiveVariables(res);
	}
	
	@Override
	public AnalysisDomain edgeEffectStore(CFGEdge edge) {
		Set<Variable> res = new HashSet<Variable>(); 
		res.addAll(((TrulyLiveVariables) edge.getToNode().getAnalysisInfo()).getTrulyLiveVar());
		Set<Expression> exp = new HashSet<Expression>();
		exp.addAll(edge.getLabel().getInvolvedExpressions());
		for(Expression e:exp){
			res.addAll(e.getInvolvedVariables());
		}
		return new TrulyLiveVariables(res);
	}

	@Override
	public void postTransformCFG(CFG cfg) { 
		for (CFGNode node : cfg.getAllNodes()) {
			TrulyLiveVariables info = (TrulyLiveVariables) node.getAnalysisInfo();
			Set<Variable> dead = new HashSet<Variable>();
			dead.addAll(GlobalConfig.variableSet);
			dead.removeAll(info.getTrulyLiveVar());
			for (CFGEdge edge : node.getInEdges()) {
				if (edge.getLabel() instanceof LabAssign) {
					LabAssign lab = (LabAssign) edge.getLabel();
					if (dead.contains(lab.getLeftSide())) {
						edge.setLabel(new LabNop());
					}
				}
			}
		}
	}
}
